-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Created dynamic list of posts #916
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job, but there is quite a bit of work to be done.
src/App.tsx
Outdated
onClick={() => { | ||
if (isDropDownActive) { | ||
setIsDropDownActive(false); | ||
} | ||
}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This callback should be moved to a separate handler.
src/App.tsx
Outdated
Something went wrong! | ||
</div> | ||
)} | ||
{(selectedUser && !postsLoadingError) && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For better readability, conditions where there are more than two values should be assigned to separate variables.
src/components/NewCommentForm.tsx
Outdated
const [name, setName] = useState(''); | ||
const [email, setEmail] = useState(''); | ||
const [commentText, setCommentText] = useState(''); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to have a managed state for one entity with simple inputs, it is better to keep everything in one state.
src/components/NewCommentForm.tsx
Outdated
createComment({ | ||
name, | ||
email, | ||
body: commentText, | ||
postId: selectedPost.id, | ||
}).then((newComment) => { | ||
setComments(prev => [...prev, newComment]); | ||
}).finally(() => { | ||
setCommentText(''); | ||
setIsSubmitted(false); | ||
setIsLoading(false); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a catch
block to catch possible errors and (optional) try to use try...catch
construction (I think this design is more convenient for long records).
src/components/PostDetails.tsx
Outdated
{isLoading ? ( | ||
<Loader /> | ||
) : ( | ||
<> | ||
{hasError ? ( | ||
<div className="notification is-danger" data-cy="CommentsError"> | ||
Something went wrong | ||
</div> | ||
) : ( | ||
<> | ||
{ | ||
comments.length ? ( | ||
<> | ||
<p className="title is-4">Comments:</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything works fine now, but it's written with a lot of nesting, so it's difficult to read and maintain in the future.
Try to avoid this by moving part of the code to the new Comments
component.
src/components/PostsList.tsx
Outdated
<Loader /> | ||
) : ( | ||
<div data-cy="PostsList"> | ||
{posts.length > 0 ? ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{posts.length > 0 ? ( | |
{posts.length ? ( |
src/components/UserSelector.tsx
Outdated
getUsers() | ||
.then(setAllUsers); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a catch
block.
src/components/UserSelector.tsx
Outdated
> | ||
{user.name} | ||
</a> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great 🔥
Let's slightly improve your solution and fix some issues
src/App.tsx
Outdated
const [isNewCommentActive, setIsNewCommentActive] = useState(false); | ||
const [isDropDownActive, setIsDropDownActive] = useState(false); | ||
|
||
const [postsLoadingError, setPostsLoadingError] = useState(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isPostsLoadingError
or just isError
src/components/Comments.tsx
Outdated
<article | ||
className="message is-small" | ||
data-cy="Comment" | ||
key={comment.id} | ||
> | ||
<div className="message-header"> | ||
<a href={`mailto:${comment.email}`} data-cy="CommentAuthor"> | ||
{comment.name} | ||
</a> | ||
<button | ||
data-cy="CommentDelete" | ||
type="button" | ||
className="delete is-small" | ||
aria-label="delete" | ||
onClick={() => onDelete(comment.id)} | ||
> | ||
delete button | ||
</button> | ||
</div> | ||
|
||
<div className="message-body" data-cy="CommentBody"> | ||
{comment.body} | ||
</div> | ||
</article> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create a component Comment
export const useComments = () => { | ||
const comments = useContext(CommentsContext); | ||
|
||
return comments; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export const useComments = () => { | |
const comments = useContext(CommentsContext); | |
return comments; | |
}; | |
export const useComments = () => useContext(CommentsContext); |
src/components/NewCommentForm.tsx
Outdated
const newComment = await createComment({ | ||
name, | ||
email, | ||
body: commentText, | ||
postId: selectedPost.id, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Request to server must be inside try/catch
block to catch server errors
src/components/NewCommentForm.tsx
Outdated
const handleChangeName = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
setCommentForm(prev => ({ ...prev, name: event.target.value })); | ||
}; | ||
|
||
const handleChangeEmail = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
setCommentForm(prev => ({ ...prev, email: event.target.value })); | ||
}; | ||
|
||
const handleChangeComment = ( | ||
event: React.ChangeEvent<HTMLTextAreaElement>, | ||
) => { | ||
setCommentForm(prev => ({ ...prev, commentText: event.target.value })); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can combine them into one handler. Input name you can get from event.target
. Something like this
const handleChange = (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const { name, value } = event.target;
setCommentForm(prev => ({ ...prev, [name]: value }));
};
src/components/PostDetails.tsx
Outdated
const toggleAddCommentForm = () => { | ||
onAddComment(true); | ||
}; | ||
|
||
<article className="message is-small" data-cy="Comment"> | ||
<div className="message-header"> | ||
<a | ||
href="mailto:misha@mate.academy" | ||
data-cy="CommentAuthor" | ||
> | ||
Misha Hrynko | ||
</a> | ||
const handleDeleteComment = (commentId: number) => { | ||
setComments(prev => prev.filter(({ id }) => id !== commentId)); | ||
onDeleteComment(commentId); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't find where you catch API errors, if you don't catch an error your app can crash.
Also, filter comments only if the comment was successfully deleted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is written in the task to filter comments before delete
Implement comment deletion
Delete the commnet immediately not waiting for the server response to improve the UX.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is written in the task to filter comments before delete
Implement comment deletion
Delete the commnet immediately not waiting for the server response to improve the UX.
Sure, but you still need to handle possible API errors, and if actually comment wasn't added or deleted user should know about it. As it's weird if I add a comment, see it, then reload the page, and my comment disappears
setIsLoading(true); | ||
if (selectedPost) { | ||
getComments(selectedPost.id) | ||
.then(setComments) | ||
.catch(() => setHasError(true)) | ||
.finally(() => setIsLoading(false)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setIsLoading(true); | |
if (selectedPost) { | |
getComments(selectedPost.id) | |
.then(setComments) | |
.catch(() => setHasError(true)) | |
.finally(() => setIsLoading(false)); | |
} | |
if (selectedPost) { | |
setIsLoading(true); | |
getComments(selectedPost.id) | |
.then(setComments) | |
.catch(() => setHasError(true)) | |
.finally(() => setIsLoading(false)); | |
} |
If selectedPost
is null
the isLoading
state doesn't change to false.
But, as I see selectedPost
is a required prop, so you can get rid of if (selectedPost)
check
{`#${selectedPost?.id}: ${selectedPost?.title}`} | ||
</h2> | ||
|
||
<p data-cy="PostBody"> | ||
{selectedPost?.body} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need these checks selectedPost?
, in your Props type this prop is required
src/components/PostsList.tsx
Outdated
<tbody> | ||
{posts.map((post) => { | ||
return ( | ||
<tr data-cy="Post" key={post.id}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's create a Post
component
src/App.tsx
Outdated
setSelectedUser(null); | ||
setSelectedUser(newUser); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you set it to null
, it doesn't work as expected in this case and makes no sense
src/App.tsx
Outdated
<PostsList | ||
selectedPost={selectedPost} | ||
selectedUser={selectedUser} | ||
onSelectPost={(post) => setSelectedPost(post)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onSelectPost={(post) => setSelectedPost(post)} | |
onSelectPost={setSelectedPost} |
} | ||
|
||
setCommentForm(prev => ({ ...prev, commentText: '' })); | ||
setIsSubmitted(false); | ||
setIsLoading(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be done in the finally
block
src/components/PostDetails.tsx
Outdated
const toggleAddCommentForm = () => { | ||
onAddComment(true); | ||
}; | ||
|
||
<article className="message is-small" data-cy="Comment"> | ||
<div className="message-header"> | ||
<a | ||
href="mailto:misha@mate.academy" | ||
data-cy="CommentAuthor" | ||
> | ||
Misha Hrynko | ||
</a> | ||
const handleDeleteComment = (commentId: number) => { | ||
setComments(prev => prev.filter(({ id }) => id !== commentId)); | ||
onDeleteComment(commentId); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is written in the task to filter comments before delete
Implement comment deletion
Delete the commnet immediately not waiting for the server response to improve the UX.
Sure, but you still need to handle possible API errors, and if actually comment wasn't added or deleted user should know about it. As it's weird if I add a comment, see it, then reload the page, and my comment disappears
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good work 👍
But, you should handle API errors in any case.
Also, left a few comments above
const toggleAddCommentForm = () => { | ||
onAddComment(true); | ||
}; | ||
|
||
const handleDeleteComment = (commentId: number) => { | ||
setComments(prev => prev.filter(({ id }) => id !== commentId)); | ||
onDeleteComment(commentId); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still don't see where you handle API errors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good work 👍
const handleDeleteComment = (commentId: number) => { | ||
setComments(prev => prev.filter(({ id }) => id !== commentId)); | ||
try { | ||
onDeleteComment(commentId); | ||
} catch { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const handleDeleteComment = (commentId: number) => { | |
setComments(prev => prev.filter(({ id }) => id !== commentId)); | |
try { | |
onDeleteComment(commentId); | |
} catch { | |
const handleDeleteComment = async (commentId: number) => { | |
setComments(prev => prev.filter(({ id }) => id !== commentId)); | |
try { | |
await onDeleteComment(commentId); | |
} catch { |
onDeleteComment
is an async operation so you should wait for the result.
For this task it's okay, but in real life, if something goes wrong you should notify the user and get back previous data, for example, if a comment wasn't added successfully, you should remove it from the UI. The same if the deletion fails you should get back the comment and show it, and notify a user that the error occurred
DEMO LINK